AGENTS.md - Project Guidelines

⚠️ IMPORTANT: Documentation Rules

DO NOT create summary/documentation MD files unless explicitly requested by user.

Examples of files to AVOID creating:

Only create MD files when:

Instead:

Recent Performance & UX Improvements (Dec 2024)

✅ Implemented Improvements

  1. Keyboard Shortcuts - Ctrl+N untuk new invoice, Escape untuk back
  2. Better Loading States - Separate loading untuk invoices vs updating
  3. Improved Toast Notifications - Dengan descriptions yang lebih informatif
  4. Enhanced Empty States - Visual yang lebih engaging dengan CTA
  5. Smooth Page Transitions - Fade-in animations untuk form/preview
  6. Optimized Bundle - Package imports optimization untuk lucide-react
  7. Better Touch Feedback - Active states dan touch-action optimization
  8. Image Optimization - WebP/AVIF support dengan 30-day cache
  9. Tooltip Hints - Keyboard shortcuts hints di buttons
  10. Error Boundary - Graceful error handling dengan fallback UI

📦 New Utilities

🎨 Admin Features

🔒 Template Access Control

🎯 Next Improvements (Optional)


AGENTS.md - Project Guidelines

General Rules

Tech Stack

UI Components

Data Fetching Pattern (Next.js 16 + React Query)

Pattern ini mengoptimalkan navigasi dengan client-side caching via React Query. Page server component hanya render shell, data fetching dilakukan di client.

1. Page Component (Static Shell)

// app/[feature]/page.tsx
import { FeatureClient } from './feature-client'

// Page hanya render client component dengan initialData null
// Data fetching dilakukan di client via React Query
export default function FeaturePage() {
  return <FeatureClient initialData={null} />
}

2. Loading State (loading.tsx)

// app/[feature]/loading.tsx
import { FeatureSkeleton } from '@/components/skeletons/feature-skeleton'

export default function FeatureLoading() {
  return <FeatureSkeleton />
}

3. React Query Hook dengan Cache Check

// lib/hooks/use-feature-data.ts
'use client'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import { useRef } from 'react'

export const featureKeys = {
  all: ['feature'] as const,
  data: () => [...featureKeys.all, 'data'] as const,
}

export function useFeatureData<T>(initialData?: T) {
  const queryClient = useQueryClient()
  
  // Check if cache exists - don't overwrite with initialData
  const existingData = queryClient.getQueryData<T>(featureKeys.data())
  const initialDataRef = useRef(existingData ? undefined : initialData)
  
  return useQuery({
    queryKey: featureKeys.data(),
    queryFn: async () => {
      const { getFeatureDataAction } = await import('@/app/actions/feature')
      const result = await getFeatureDataAction()
      if (!result.success) throw new Error(result.error)
      return result.data as T
    },
    initialData: initialDataRef.current,
    staleTime: 5 * 60 * 1000, // 5 menit
    gcTime: 10 * 60 * 1000, // 10 menit
    refetchOnMount: false,
    refetchOnWindowFocus: false,
  })
}

export function useInvalidateFeature() {
  const queryClient = useQueryClient()
  return () => queryClient.invalidateQueries({ queryKey: featureKeys.all })
}

4. Client Component (PENTING: Hooks Order)

// app/[feature]/feature-client.tsx
'use client'
import { useState, useEffect, useCallback } from 'react'
import { useFeatureData, useInvalidateFeature } from '@/lib/hooks/use-feature-data'
import { useAuth } from '@/lib/auth/auth-context'
import { FeatureSkeleton } from '@/components/skeletons/feature-skeleton'

interface FeatureClientProps {
  initialData: FeatureData | null
}

export function FeatureClient({ initialData }: FeatureClientProps) {
  // ⚠️ SEMUA HOOKS HARUS DIPANGGIL DULU sebelum conditional return
  const { data, isLoading } = useFeatureData(initialData ?? undefined)
  const { user, loading: authLoading } = useAuth()
  const invalidate = useInvalidateFeature()
  
  // State hooks
  const [someState, setSomeState] = useState(false)
  
  // Effect hooks
  useEffect(() => {
    // side effects
  }, [])
  
  // Callback hooks
  const handleAction = useCallback(() => {
    // action logic
  }, [])

  // ✅ Conditional return SETELAH semua hooks
  if (authLoading || (isLoading && !data)) {
    return <FeatureSkeleton />
  }

  if (!user) {
    return null
  }

  // Extract data setelah loading check
  const featureData = data || initialData

  return (
    // ... render UI
  )
}

5. Server Action untuk Data Fetching

// app/actions/feature.ts
'use server'
import { createClient } from '@/lib/supabase/server'

export async function getFeatureDataAction() {
  try {
    const supabase = await createClient()
    const { data: { user } } = await supabase.auth.getUser()
    
    if (!user) {
      return { success: false, error: 'Unauthorized' }
    }
    
    // ... fetch logic
    return { success: true, data }
  } catch (error) {
    return { success: false, error: 'Failed to fetch' }
  }
}

6. Mutation dengan Cache Invalidation

const invalidate = useInvalidateFeature()

const handleSave = async () => {
  const result = await saveFeatureAction(data)
  if (result.success) {
    invalidate() // Invalidate React Query cache
  }
}

7. Proxy untuk Session Management (proxy.ts)

// proxy.ts (Next.js 16 - replaces middleware.ts)
import { type NextRequest } from "next/server"
import { updateSession } from "@/lib/supabase/middleware"

export async function proxy(request: NextRequest) {
  // Only refresh session cookies - no auth checks
  const response = await updateSession(request)
  return response
}

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|api/|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
}

⚠️ ATURAN PENTING

  1. Hooks Order: Semua hooks (useState, useEffect, useCallback, useQuery, dll) HARUS dipanggil sebelum conditional return apapun. Ini adalah aturan React.

  2. No Server Fetch di Page: Jangan fetch data di server component untuk pages yang butuh auth. Biarkan client component handle via React Query.

  3. Cache Check: Gunakan useRef untuk menyimpan initialData hanya sekali, cek getQueryData untuk menghindari overwrite cache yang sudah ada.

  4. Loading State: Tampilkan skeleton jika authLoading atau (isLoading && !data). Kondisi !data penting agar tidak flash skeleton saat ada cache.

  5. Static Pages: Dengan pattern ini, pages menjadi Static (○) di build output, memungkinkan instant navigation tanpa server round-trip.

  6. Proxy vs Middleware: Next.js 16 menggunakan proxy.ts (bukan middleware.ts). Proxy hanya refresh session, auth check dilakukan di server actions.

🔒 Security Layers

Pattern ini aman dengan 3 layer security:

  1. Proxy (proxy.ts) - Refresh Supabase session cookies
  2. Server Actions - Validate auth sebelum return data (getUser() check)
  3. Client Components - Redirect unauthenticated users untuk UX

Trade-off:

Contoh Implementasi

File Structure

app/
├── actions/          # Server actions
├── api/              # API routes
├── dashboard/        # Protected routes
│   └── [feature]/
│       ├── page.tsx           # Server component
│       └── feature-client.tsx # Client component
lib/
├── db/
│   ├── data-access/  # Data fetching functions + cache tags
│   └── services/     # Database service classes
├── supabase/
│   ├── client.ts     # Browser client
│   ├── server.ts     # Server client
│   └── middleware.ts # Auth middleware
├── stores/           # Zustand stores
└── utils/            # Utility functions
components/
├── ui/               # shadcn/ui components
└── features/         # Feature-specific components

Authentication

Testing

Server Actions Pattern

'use server'

export async function myAction(data: FormData) {
  const supabase = await createClient()
  const { data: { user }, error: authError } = await supabase.auth.getUser()

  if (authError || !user) {
    return { success: false, error: 'Unauthorized' }
  }

  // ... action logic

  revalidateTag(CACHE_TAGS.feature)
  revalidatePath('/dashboard/feature')
  return { success: true, data: result }
}

Form Handling

Environment Variables